﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XNADota.Input;

namespace XNADota.Camera
{
    public abstract class BaseCamera : IInputHandler
    {
        #region constants

        // constants
        public static readonly Vector3 DefaultPosition = new Vector3(500, 0, 0);
        public static readonly Vector3 DefaultUp = new Vector3(0, 1, 0);
        public static readonly Vector3 DefaultLookAt = new Vector3(0, 0, 0);

        #endregion // constants

        #region private members

        private Game _Game;

        protected int _ScreenWidth;
        protected int _ScreenHeight;

        protected Vector3 _Position = BaseCamera.DefaultPosition;
        protected Vector3 _LookAt = BaseCamera.DefaultLookAt;
        protected Vector3 _Up = BaseCamera.DefaultUp;

        protected Matrix _View = Matrix.Identity;
        protected Matrix _Projection = Matrix.Identity;

        // projection matrix
        protected float _NearClip = 1.0f;
        protected float _FarClip = 30000.0f;
        protected float _FieldOfView = MathHelper.ToRadians(45.0f);
        protected float _AspectRatio = 4.0f / 3.0f;

        private BoundingFrustum _Frustrum;

        #endregion

        #region  setters & getters
        public Vector3 Position
        {
            set { _Position = value; }
            get { return _Position; }
        }

        public Vector3 LookAt
        {
            set { _LookAt = value; }
            get { return _LookAt; }
        }

        public Vector3 Up
        {
            set { _Up = value; }
            get { return _Up; }
        }

        public Matrix View
        {
            get { return _View; }
        }

        public Matrix Projection
        {
            get { return _Projection; }
        }

        public float AspectRatio
        {
            set { _AspectRatio = value; }
            get { return _AspectRatio; }
        }

        public BoundingFrustum BoundingFrustum
        {
            get { return _Frustrum; }
        }

        #endregion // setters & getters

        public BaseCamera(Game game)
        {
            _Game = game;
            _AspectRatio = _Game.GraphicsDevice.Viewport.AspectRatio;
            _ScreenWidth = _Game.GraphicsDevice.Viewport.Width;
            _ScreenHeight = _Game.GraphicsDevice.Viewport.Height;
            _Projection = Matrix.CreatePerspectiveFieldOfView(_FieldOfView, _AspectRatio, _NearClip, _FarClip);
            _View = Matrix.CreateLookAt(_Position, _LookAt, _Up);
            _Frustrum = new BoundingFrustum(View * Projection);
        }

        public virtual void Update(Microsoft.Xna.Framework.GameTime gameTime)
        {
            _View = Matrix.CreateLookAt(_Position, _LookAt, _Up);
            _Frustrum = new BoundingFrustum(View * Projection);
        }

        public virtual void ProcessInput(GameTime gameTime, InputState inputState) 
        {
            throw new NotImplementedException("ProcessInput method of BaseCamera is not implemented.");
        }

        public Ray GetRayFromScreen(int x, int y)
        {
            if (x < 0) x = 0;
            if (x > _ScreenWidth) x = _ScreenWidth;
            if (y < 0) y = 0;
            if (y > _ScreenHeight) y = _ScreenHeight;

            Vector2 screenPosition = new Vector2(x, y);

            Vector3 nearSource = new Vector3(screenPosition, 0f);
            Vector3 farSource = new Vector3(screenPosition, 1f);

            // use Viewport.Unproject to tell what those two screen space positions
            // would be in world space. we'll need the projection matrix and view
            // matrix, which we have saved as member variables. We also need a world
            // matrix, which can just be identity.
            Vector3 nearPoint = this._Game.GraphicsDevice.Viewport.Unproject(nearSource,
                this._Projection, this._View, Matrix.Identity);

            Vector3 farPoint = this._Game.GraphicsDevice.Viewport.Unproject(farSource,
                this._Projection, this._View, Matrix.Identity);

            // find the direction vector that goes from the nearPoint to the farPoint
            // and normalize it....
            Vector3 direction = farPoint - nearPoint;
            direction.Normalize();

            // and then create a new ray using nearPoint as the source.
            return new Ray(nearPoint, direction);

            //float dx, dy;
            //dx = (float)Math.Tan(_FieldOfView / 2) * (x * 2.0f / _ScreenWidth - 1.0f) / _AspectRatio;
            //dy = (float)Math.Tan(_FieldOfView / 2) * (1.0f - y * 2 / _ScreenHeight);

            //Vector3 nearPoint = new Vector3(dx * _NearClip, dy * _NearClip, _NearClip);
            //Vector3 farPoint = new Vector3(dy * _FarClip, dy * _FarClip, _FarClip);

            //Matrix invertView = Matrix.Invert(_View);
            //nearPoint = Vector3.Transform(nearPoint, invertView);
            //farPoint = Vector3.Transform(farPoint, invertView);

            //Vector3 dir = (farPoint - nearPoint);
            //dir.Normalize();
            //return new Ray(_Position, dir);
        }
    }
}
